use std::collections::HashMap;
use std::error::Error;
use std::fmt::{self, Display};

use crate::core::{
    analysis::matching::{
        BOOL,
        DECIMAL,
        NULL,
        NUM,
        STRING,
        VAR,
    },
    base::util,
    model::err::YuzuError,
};
use crate::core::base::meta;

pub type YuzuStruct = (String, HashMap<String, Data>);

#[derive(Clone, Debug)]
pub enum Data {
    Null(DataType),
    Num(isize),
    Decimal(f64),
    Bool(bool),
    String(String),
    Yuzu(YuzuStruct),
}

impl Display for Data {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Data::Num(i) => { write!(f, "{}", i) }
            Data::Decimal(d) => { write!(f, "{}", d) }
            Data::Bool(b) => { write!(f, "{}", b) }
            Data::String(s) => { write!(f, "{}", s) }
            Data::Null(t) => { write!(f, "{}", t) }
            Data::Yuzu((n, _)) => { write!(f, "{}", n) }
        }
    }
}

impl Data {
    pub fn transfer(yuzu: &str, value: &str) -> Result<Self, Box<dyn Error>> {
        if NUM.is_match(value) {
            Ok(Data::Num(value.parse()?))
        } else if DECIMAL.is_match(value) {
            Ok(Data::Decimal(value.parse()?))
        } else if NULL.is_match(value) {
            Ok(null_to_data(value))
        } else if BOOL.is_match(value) {
            Ok(Data::Bool(value.parse()?))
        } else if STRING.is_match(value) {
            let s = value.replace("'", "");
            Ok(Data::String(s))
        } else if VAR.is_match(value) {
            Ok(meta::get_var_exist(yuzu, value))
        } else {
            Err(YuzuError::new(format!("can not parse data type with : `{}`", value)))
        }
    }

    pub fn get_type_name(&self) -> String {
        match self {
            Data::Num(_) => { String::from("num") }
            Data::Decimal(_) => { String::from("decimal") }
            Data::Bool(_) => { String::from("bool") }
            Data::Null(t) => { format!("nul!{}", t) }
            Data::String(_) => { String::from("string") }
            Data::Yuzu((n, _)) => { n.to_string() }
        }
    }

    pub fn get_data_type(&self) -> DataType {
        match self {
            Data::Null(t) => { t.clone() }
            Data::Num(_) => { DataType::Num }
            Data::Decimal(_) => { DataType::Decimal }
            Data::Bool(_) => { DataType::Bool }
            Data::String(_) => { DataType::String }
            Data::Yuzu((n, _)) => { DataType::Yuzu(n.clone()) }
        }
    }
}

#[derive(Debug, Clone)]
pub enum DataType {
    Num,
    Decimal,
    Bool,
    String,
    Yuzu(String),
}

impl Display for DataType {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            DataType::Num => { write!(f, "t_num") }
            DataType::Decimal => { write!(f, "t_decimal") }
            DataType::Bool => { write!(f, "t_bool") }
            DataType::String => { write!(f, "t_string") }
            DataType::Yuzu(y) => { write!(f, "t_{}", y) }
        }
    }
}

impl DataType {
    pub fn from(type_name: &str) -> Self {
        match type_name {
            "num" => { Self::Num }
            "bool" => { Self::Bool }
            "string" => { Self::String }
            "decimal" => { Self::Decimal }
            _ => { Self::Yuzu(type_name.to_string()) }
        }
    }

    pub fn to(&self, item: &str) -> Result<Data, Box<dyn Error>> {
        match self {
            DataType::Num => { Ok(Data::Num(item.parse()?)) }
            DataType::Decimal => { Ok(Data::Decimal(item.parse()?)) }
            DataType::Bool => { Ok(Data::Bool(item.parse()?)) }
            DataType::String => { Ok(Data::String(item.to_string())) }
            DataType::Yuzu(yuzu) => { Err(YuzuError::new(format!("this yuzu transfer is deploying : {}", yuzu))) }
        }
    }
}

/// 将空表达式转为数据 nul![data_type] =>`Data`
pub fn null_to_data(null: &str) -> Data {
    let capture = NULL.captures(null).unwrap();
    let type_name = util::capture_str(&capture, 1);
    let data_type = DataType::from(&type_name);
    Data::Null(data_type)
}